﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Windows.Forms;

namespace AMM.CustomClassLibrary {
    internal static class FlexReflector {

        public static string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> selector) {
            System.Linq.Expressions.MemberExpression expression = null;

            expression = GetMemberExpression(selector);

            return expression.Member.Name;
        }

        public static string GetPropertyName<T>(Expression<Func<T, object>> selector) {
            MemberExpression expression = null;

            expression = GetMemberExpression(selector);

            return expression.Member.Name;
        }

        private static MemberExpression GetMemberExpression(Expression selector) {
            LambdaExpression lambdaExpression = selector as LambdaExpression;

            if (lambdaExpression == null) {
                throw new ArgumentNullException("The selector is not a valid lambda expression.");
            }

            MemberExpression memberExpression = null;

            if (lambdaExpression.Body.NodeType == ExpressionType.Convert) {
                memberExpression = ((UnaryExpression)lambdaExpression.Body).Operand as MemberExpression;
            }
            else if (lambdaExpression.Body.NodeType == ExpressionType.MemberAccess) {
                memberExpression = lambdaExpression.Body as MemberExpression;
            }

            if (memberExpression == null) {
                throw new ArgumentException("The property is not a valid property to be extracted by a lambda expression.");
            }

            return memberExpression;
        }

    }

    internal static class FlexReflectorExtensions {
        public static string GetPropertyName<T>(this T instance, Expression<Func<T, object>> propertyAccesor) {
            return FlexReflector.GetPropertyName(propertyAccesor);
        }
    }

    public class FlexBindingSource<TDataSource> {
        private BindingSource _winFormsBindingSource;
        public FlexBindingSource() {
            this._winFormsBindingSource = new BindingSource();
            this._winFormsBindingSource.DataSource = typeof(TDataSource);
        }


        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstance">The control instance on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        public void CreateBinding<TControl, TControlProperty, TModelProperty>(TControl controlInstance, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor)
            where TControl : Control {
            this.CreateBinding(controlInstance, controlPropertyAccessor, datasourceMemberAccesor, DataSourceUpdateMode.OnValidation);
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstance">The control instance on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        /// <param name="dataSourceUpdateMode">See control.DataBindings.Add method </param>
        public void CreateBinding<TControl, TControlProperty, TModelProperty>(TControl controlInstance, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor, DataSourceUpdateMode dataSourceUpdateMode)
            where TControl : Control {
            this.CreateBinding(controlInstance, controlPropertyAccessor, datasourceMemberAccesor, null, dataSourceUpdateMode);
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstance">The control instance on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        /// <param name="transform">A lambda expression which transform the datasource member value into a custom action</param>
        public void CreateBinding<TControl, TControlProperty, TModelProperty>(TControl controlInstance, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor, Func<TModelProperty, TControlProperty> transform)
                where TControl : Control {
            this.CreateBinding(controlInstance, controlPropertyAccessor, datasourceMemberAccesor, transform, DataSourceUpdateMode.OnValidation);
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstance">The control instance on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        /// <param name="transform">A lambda expression which transform the datasource member value into a custom action</param>
        /// <param name="dataSourceUpdateMode"></param>
        public void CreateBinding<TControl, TControlProperty, TModelProperty>(TControl controlInstance, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor, Func<TModelProperty, TControlProperty> transform, DataSourceUpdateMode dataSourceUpdateMode)
            where TControl : Control {
            string controlPropertyName = FlexReflector.GetPropertyName(controlPropertyAccessor);
            string sourcePropertyName = FlexReflector.GetPropertyName(datasourceMemberAccesor);
            Binding bind = controlInstance.DataBindings.Add(controlPropertyName, _winFormsBindingSource, sourcePropertyName, true, dataSourceUpdateMode);
            if (transform != null) {
                bind.Format += (sender, e) => e.Value = transform((TModelProperty)e.Value);
            }
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstances">The control instances enumerable on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        public void CreateBindingOverMoreControls<TControl, TControlProperty, TModelProperty>(IEnumerable<TControl> controlInstances, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor)
            where TControl : Control {
            CreateBindingOverMoreControls(controlInstances, controlPropertyAccessor, datasourceMemberAccesor, DataSourceUpdateMode.OnValidation);
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstances">The control instances enumerable on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        /// <param name="dataSourceUpdateMode">See control.DataBindings.Add method </param>
        public void CreateBindingOverMoreControls<TControl, TControlProperty, TModelProperty>(IEnumerable<TControl> controlInstances, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor, DataSourceUpdateMode dataSourceUpdateMode)
            where TControl : Control {
            CreateBindingOverMoreControls(controlInstances, controlPropertyAccessor, datasourceMemberAccesor, null, dataSourceUpdateMode);
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstances">The control instance enumerable on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        /// <param name="transform">A lambda expression which transform the datasource member value into a custom action</param>
        public void CreateBindingOverMoreControls<TControl, TControlProperty, TModelProperty>(IEnumerable<TControl> controlInstances, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor, Func<TModelProperty, TControlProperty> transform)
                where TControl : Control {
            CreateBindingOverMoreControls(controlInstances, controlPropertyAccessor, datasourceMemberAccesor, transform, DataSourceUpdateMode.OnValidation);
        }

        /// <summary>
        /// Creates a DataBinding between a control property and a datasource property
        /// </summary>
        /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam>
        /// <typeparam name="TControlProperty">The control binding property type</typeparam>
        /// <typeparam name="TModelProperty">The underlying model property type</typeparam>
        /// <param name="controlInstances">The control instance enumerable on wich the databinding will be added</param>
        /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param>
        /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param>
        /// <param name="transform">A lambda expression which transform the datasource member value into a custom action</param>
        /// <param name="dataSourceUpdateMode"></param>
        public void CreateBindingOverMoreControls<TControl, TControlProperty, TModelProperty>(IEnumerable<TControl> controlInstances, Expression<Func<TControl, TControlProperty>> controlPropertyAccessor, Expression<Func<TDataSource, TModelProperty>> datasourceMemberAccesor, Func<TModelProperty, TControlProperty> transform, DataSourceUpdateMode dataSourceUpdateMode)
            where TControl : Control {
            foreach (TControl item in controlInstances)
                CreateBinding(item, controlPropertyAccessor, datasourceMemberAccesor, transform, dataSourceUpdateMode);
        }

        private TDataSource _currentItem = default(TDataSource);
        public TDataSource CurrentItem {
            get {
                return _currentItem;
            }
            set {
                _currentItem = value;

                if (value == null)
                    this._winFormsBindingSource.DataSource = typeof(TDataSource);
                else
                    this._winFormsBindingSource.DataSource = value;
            }
        }       
    }
}